<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<bindings id="toolbarBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="toolbar-base">
    <resources>
      <stylesheet src="chrome://global/skin/toolbar.css"/>
    </resources>
  </binding>

  <binding id="toolbox" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
    <implementation>
      <field name="palette">
        null
      </field>

      <field name="toolbarset">
        null
      </field>

      <field name="customToolbarCount">
        0
      </field>

      <field name="externalToolbars">
        []
      </field>

      <!-- Set by customizeToolbar.js -->
      <property name="customizing">
        <getter><![CDATA[
          return this.getAttribute("customizing") == "true";
        ]]></getter>
        <setter><![CDATA[
          if (val)
            this.setAttribute("customizing", "true");
          else
            this.removeAttribute("customizing");
          return val;
        ]]></setter>
      </property>

      <constructor>
        <![CDATA[
          this.toolbarInfoSeparators = ["|", "-"];
          this.toolbarInfoLegacySeparator = ":";
          // Look to see if there is a toolbarset.
          this.toolbarset = this.firstChild;
          while (this.toolbarset && this.toolbarset.localName != "toolbarset") {
            this.toolbarset = this.toolbarset.nextSibling;
          }

          if (this.toolbarset) {
            // Create each toolbar described by the toolbarset.
            var index = 0;
            while (this.toolbarset.hasAttribute("toolbar" + (++index))) {
              let hiddingAttribute =
                  this.toolbarset.getAttribute("type") == "menubar"
                      ? "autohide" : "collapsed";
              let toolbarInfo = this.toolbarset.getAttribute("toolbar" + index);
              let infoSplit = toolbarInfo.split(this.toolbarInfoSeparators[0]);
              if (infoSplit.length == 1) {
                infoSplit = toolbarInfo.split(this.toolbarInfoLegacySeparator);
              }
              let infoName = infoSplit[0];
              let infoHidingAttribute = [null, null];
              let infoCurrentSet = "";
              let infoSplitLen = infoSplit.length;
              switch (infoSplitLen) {
                case 3:
                  // Pale Moon 27.2+
                  // Basilisk (UXP)
                  infoHidingAttribute = infoSplit[1]
                      .split(this.toolbarInfoSeparators[1]);
                  infoCurrentSet = infoSplit[2];
                  break;
                case 2:
                  // Legacy:
                  // - toolbars from Pale Moon 27.0 - 27.1.x
                  // - Basilisk (moebius)
                  // The previous value (hiddingAttribute) isn't stored.
                  infoHidingAttribute = [hiddingAttribute, "false"];
                  infoCurrentSet = infoSplit[1];
                  break;
                default:
                  Components.utils.reportError(
                      "Customizable toolbars - an invalid value:" + "\n"
                      + '"toolbar' + index + '" = "' + toolbarInfo + '"');
                  break;
              }
              this.appendCustomToolbar(
                  infoName, infoCurrentSet, infoHidingAttribute);
            }
          }
        ]]>
      </constructor>

      <method name="appendCustomToolbar">
        <parameter name="aName"/>
        <parameter name="aCurrentSet"/>
        <parameter name="aHidingAttribute"/>
        <body>
          <![CDATA[
            if (!this.toolbarset)
              return null;
            var toolbar = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                                  "toolbar");
            toolbar.id = "__customToolbar_" + aName.replace(" ", "_");
            toolbar.setAttribute("customizable", "true");
            toolbar.setAttribute("customindex", ++this.customToolbarCount);
            toolbar.setAttribute("toolbarname", aName);
            toolbar.setAttribute("currentset", aCurrentSet);
            toolbar.setAttribute("mode", this.getAttribute("mode"));
            toolbar.setAttribute("iconsize", this.getAttribute("iconsize"));
            toolbar.setAttribute("context", this.toolbarset.getAttribute("context"));
            toolbar.setAttribute("class", "chromeclass-toolbar");
            // Restore persist the hiding attribute.
            if (aHidingAttribute[0]) {
              toolbar.setAttribute(aHidingAttribute[0], aHidingAttribute[1]);
            }

            this.insertBefore(toolbar, this.toolbarset);
            return toolbar;
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="toolbar" role="xul:toolbar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
    <implementation>
      <property name="toolbarName"
                onget="return this.getAttribute('toolbarname');"
                onset="this.setAttribute('toolbarname', val); return val;"/>

      <field name="_toolbox">null</field>
      <property name="toolbox" readonly="true">
        <getter><![CDATA[
          if (this._toolbox)
            return this._toolbox;

          let toolboxId = this.getAttribute("toolboxid");
          if (toolboxId) {
            let toolbox = document.getElementById(toolboxId);
            if (!toolbox) {
              let tbName = this.toolbarName;
              if (tbName)
                tbName = " (" + tbName + ")";
              else
                tbName = "";
              throw new Error(`toolbar ID ${this.id}${tbName}: toolboxid attribute '${toolboxId}' points to a toolbox that doesn't exist`);
            }

            if (toolbox.externalToolbars.indexOf(this) == -1)
              toolbox.externalToolbars.push(this);

            return this._toolbox = toolbox;
          }

          return this._toolbox = (this.parentNode &&
                                  this.parentNode.localName == "toolbox") ?
                                 this.parentNode : null;
        ]]></getter>
      </property>

      <constructor>
        <![CDATA[
          if (document.readyState == "complete") {
            this._init();
          } else {
            // Need to wait until XUL overlays are loaded. See bug 554279.
            let self = this;
            document.addEventListener("readystatechange", function (event) {
              if (document.readyState != "complete")
                return;
              document.removeEventListener("readystatechange", arguments.callee, false);
              self._init();
            }, false);
          }
        ]]>
      </constructor>

      <method name="_init">
        <body>
        <![CDATA[
          // Searching for the toolbox palette in the toolbar binding because
          // toolbars are constructed first.
          var toolbox = this.toolbox;
          if (!toolbox)
            return;

          if (!toolbox.palette) {
            // Look to see if there is a toolbarpalette.
            var node = toolbox.firstChild;
            while (node) {
              if (node.localName == "toolbarpalette")
                break;
              node = node.nextSibling;
            }

            if (!node)
              return;

            // Hold on to the palette but remove it from the document.
            toolbox.palette = node;
            toolbox.removeChild(node);
          }

          // Build up our contents from the palette.
          var currentSet = this.getAttribute("currentset");
          if (!currentSet)
            currentSet = this.getAttribute("defaultset");
          if (currentSet)
            this.currentSet = currentSet;
        ]]>
        </body>
      </method>

      <method name="_idFromNode">
        <parameter name="aNode"/>
        <body>
        <![CDATA[
          if (aNode.getAttribute("skipintoolbarset") == "true")
            return "";

          switch (aNode.localName) {
            case "toolbarseparator":
              return "separator";
            case "toolbarspring":
              return "spring";
            case "toolbarspacer":
              return "spacer";
            default:
              return aNode.id;
          }
        ]]>
        </body>
      </method>

      <property name="currentSet">
        <getter>
          <![CDATA[
            var node = this.firstChild;
            var currentSet = [];
            while (node) {
              var id = this._idFromNode(node);
              if (id) {
                currentSet.push(id);
              }
              node = node.nextSibling;
            }

            return currentSet.join(",") || "__empty";
          ]]>
        </getter>

        <setter>
          <![CDATA[
            if (val == this.currentSet)
              return val;

            var ids = (val == "__empty") ? [] : val.split(",");

            var nodeidx = 0;
            var paletteItems = { }, added = { };

            var palette = this.toolbox ? this.toolbox.palette : null;

            // build a cache of items in the toolbarpalette
            var paletteChildren = palette ? palette.childNodes : [];
            for (let c = 0; c < paletteChildren.length; c++) {
              let curNode = paletteChildren[c];
              paletteItems[curNode.id] = curNode;
            }

            var children = this.childNodes;

            // iterate over the ids to use on the toolbar
            for (let i = 0; i < ids.length; i++) {
              let id = ids[i];
              // iterate over the existing nodes on the toolbar. nodeidx is the
              // spot where we want to insert items.
              let found = false;
              for (let c = nodeidx; c < children.length; c++) {
                let curNode = children[c];
                if (this._idFromNode(curNode) == id) {
                  // the node already exists. If c equals nodeidx, we haven't
                  // iterated yet, so the item is already in the right position.
                  // Otherwise, insert it here.
                  if (c != nodeidx) {
                    this.insertBefore(curNode, children[nodeidx]);
                  }

                  added[curNode.id] = true;
                  nodeidx++;
                  found = true;
                  break;
                }
              }
              if (found) {
                // move on to the next id
                continue;
              }

              // the node isn't already on the toolbar, so add a new one.
              var nodeToAdd = paletteItems[id] || this._getToolbarItem(id);
              if (nodeToAdd && !(nodeToAdd.id in added)) {
                added[nodeToAdd.id] = true;
                this.insertBefore(nodeToAdd, children[nodeidx] || null);
                nodeToAdd.setAttribute("removable", "true");
                nodeidx++;
              }
            }

            // remove any leftover removable nodes
            for (let i = children.length - 1; i >= nodeidx; i--) {
              let curNode = children[i];

              let curNodeId = this._idFromNode(curNode);
              // skip over fixed items
              if (curNodeId && curNode.getAttribute("removable") == "true") {
                if (palette)
                  palette.appendChild(curNode);
                else
                  this.removeChild(curNode);
              }
            }

            return val;
          ]]>
        </setter>
      </property>

      <field name="_newElementCount">0</field>
      <method name="_getToolbarItem">
        <parameter name="aId"/>
        <body>
          <![CDATA[
            const XUL_NS = "http://www.mozilla.org/keymaster/" +
                           "gatekeeper/there.is.only.xul";

            var newItem = null;
            switch (aId) {
              // Handle special cases
              case "separator":
              case "spring":
              case "spacer":
                newItem = document.createElementNS(XUL_NS, "toolbar" + aId);
                // Due to timers resolution Date.now() can be the same for
                // elements created in small timeframes.  So ids are
                // differentiated through a unique count suffix.
                newItem.id = aId + Date.now() + (++this._newElementCount);
                if (aId == "spring")
                  newItem.flex = 1;
                break;
              default:
                var toolbox = this.toolbox;
                if (!toolbox)
                  break;

                // look for an item with the same id, as the item may be
                // in a different toolbar.
                var item = document.getElementById(aId);
                if (item && item.parentNode &&
                    item.parentNode.localName == "toolbar" &&
                    item.parentNode.toolbox == toolbox) {
                  newItem = item;
                  break;
                }

                if (toolbox.palette) {
                  // Attempt to locate an item with a matching ID within
                  // the palette.
                  let paletteItem = this.toolbox.palette.firstChild;
                  while (paletteItem) {
                    if (paletteItem.id == aId) {
                      newItem = paletteItem;
                      break;
                    }
                    paletteItem = paletteItem.nextSibling;
                  }
                }
                break;
            }

            return newItem;
          ]]>
        </body>
      </method>

      <method name="insertItem">
        <parameter name="aId"/>
        <parameter name="aBeforeElt"/>
        <parameter name="aWrapper"/>
        <body>
          <![CDATA[
            var newItem = this._getToolbarItem(aId);
            if (!newItem)
              return null;

            var insertItem = newItem;
            // make sure added items are removable
            newItem.setAttribute("removable", "true");

            // Wrap the item in another node if so inclined.
            if (aWrapper) {
              aWrapper.appendChild(newItem);
              insertItem = aWrapper;
            }

            // Insert the palette item into the toolbar.
            if (aBeforeElt)
              this.insertBefore(insertItem, aBeforeElt);
            else
              this.appendChild(insertItem);

            return newItem;
          ]]>
        </body>
      </method>

      <method name="hasCustomInteractiveItems">
        <parameter name="aCurrentSet"/>
        <body><![CDATA[
          if (aCurrentSet == "__empty")
            return false;

          var defaultOrNoninteractive = (this.getAttribute("defaultset") || "")
                                          .split(",")
                                          .concat(["separator", "spacer", "spring"]);
          return aCurrentSet.split(",").some(function (item) {
            return defaultOrNoninteractive.indexOf(item) == -1;
          });
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="toolbar-menubar-autohide"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <implementation>
      <constructor>
        this._setInactive();
      </constructor>
      <destructor>
        this._setActive();
      </destructor>

      <field name="_inactiveTimeout">null</field>

      <field name="_contextMenuListener"><![CDATA[({
        toolbar: this,
        contextMenu: null,

        get active () {
          return !!this.contextMenu;
        },

        init: function (event) {
          var node = event.target;
          while (node != this.toolbar) {
            if (node.localName == "menupopup")
              return;
            node = node.parentNode;
          }

          var contextMenuId = this.toolbar.getAttribute("context");
          if (!contextMenuId)
            return;

          this.contextMenu = document.getElementById(contextMenuId);
          if (!this.contextMenu)
            return;

          this.contextMenu.addEventListener("popupshown", this, false);
          this.contextMenu.addEventListener("popuphiding", this, false);
          this.toolbar.addEventListener("mousemove", this, false);
        },
        handleEvent: function (event) {
          switch (event.type) {
            case "popupshown":
              this.toolbar.removeEventListener("mousemove", this, false);
              break;
            case "popuphiding":
            case "mousemove":
              this.toolbar._setInactiveAsync();
              this.toolbar.removeEventListener("mousemove", this, false);
              this.contextMenu.removeEventListener("popuphiding", this, false);
              this.contextMenu.removeEventListener("popupshown", this, false);
              this.contextMenu = null;
              break;
          }
        }
      })]]></field>

      <method name="_setInactive">
        <body><![CDATA[
          this.setAttribute("inactive", "true");
        ]]></body>
      </method>

      <method name="_setInactiveAsync">
        <body><![CDATA[
          this._inactiveTimeout = setTimeout(function (self) {
            if (self.getAttribute("autohide") == "true") {
              self._inactiveTimeout = null;
              self._setInactive();
            }
          }, 0, this);
        ]]></body>
      </method>

      <method name="_setActive">
        <body><![CDATA[
          if (this._inactiveTimeout) {
            clearTimeout(this._inactiveTimeout);
            this._inactiveTimeout = null;
          }
          this.removeAttribute("inactive");
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="DOMMenuBarActive"     action="this._setActive();"/>
      <handler event="popupshowing"         action="this._setActive();"/>
      <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
      <handler event="DOMMenuBarInactive"><![CDATA[
        if (!this._contextMenuListener.active)
          this._setInactiveAsync();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="toolbar-drag"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar">
    <implementation>
      <field name="_dragBindingAlive">true</field>
      <constructor><![CDATA[
        if (!this._draggableStarted) {
          this._draggableStarted = true;
          try {
            let tmp = {};
            Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
            let draggableThis = new tmp.WindowDraggingElement(this);
            draggableThis.mouseDownCheck = function(e) {
              // Don't move while customizing.
              return this._dragBindingAlive &&
                     this.getAttribute("customizing") != "true";
            };
          } catch (e) {}
        }
      ]]></constructor>
    </implementation>
  </binding>

  <binding id="menubar" role="xul:menubar"
           extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:menubar">
    <implementation>
       <field name="_active">false</field>
       <field name="_statusbar">null</field>
       <field name="_originalStatusText">null</field>
       <property name="statusbar" onget="return this.getAttribute('statusbar');"
                                  onset="this.setAttribute('statusbar', val); return val;"/>
       <method name="_updateStatusText">
          <parameter name="itemText"/>
          <body>
           <![CDATA[
            if (!this._active)
                return;
            var newText = itemText ? itemText : this._originalStatusText;
            if (newText != this._statusbar.label)
                this._statusbar.label = newText;
           ]]>
          </body>
        </method>
    </implementation>
    <handlers>
        <handler event="DOMMenuBarActive">
          <![CDATA[
            if (!this.statusbar) return;
            this._statusbar = document.getElementById(this.statusbar);
            if (!this._statusbar)
              return;
            this._active = true;
            this._originalStatusText = this._statusbar.label;
          ]]>
        </handler>
        <handler event="DOMMenuBarInactive">
          <![CDATA[
            if (!this._active)
              return;
            this._active = false;
            this._statusbar.label = this._originalStatusText;
          ]]>
        </handler>
        <handler event="DOMMenuItemActive">this._updateStatusText(event.target.statusText);</handler>
        <handler event="DOMMenuItemInactive">this._updateStatusText("");</handler>
    </handlers>
  </binding>

  <binding id="toolbardecoration" role="xul:toolbarseparator" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
  </binding>

  <binding id="toolbarpaletteitem" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:button">
    <content>
      <xul:hbox class="toolbarpaletteitem-box" flex="1" xbl:inherits="type,place">
        <children/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="toolbarpaletteitem-palette" extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
    <content>
      <xul:hbox class="toolbarpaletteitem-box" xbl:inherits="type,place">
        <children/>
      </xul:hbox>
      <xul:label xbl:inherits="value=title"/>
    </content>
  </binding>

</bindings>

